15.4 Ereignisse grafischer Windows-Komponenten
 
Programme mit grafischer Benutzeroberfläche (GUI – Graphical User Interface) verwenden ein ereignisorientiertes Eingabemodell, bei dem nach der Initialisierung des Programms sämtliche Operationen nur noch als Reaktionen auf Ereignisse ausgeführt werden. Jeder Eingabe ist mindestens ein Ereignis zugeordnet, das wiederum mit einer Methode, dem Ereignishandler, verknüpft sein kann, aber nicht unbedingt verbunden sein muss.
Kommt es zur Auslösung des Ereignisses, wird die registrierte Methode aufgerufen und ausgeführt. Erst wenn die Ausführung des Ereignishandlers beendet ist, kann das nächste anstehende Ereignis seinen Ereignishandler aufrufen. Solange kein Ereignis ausgelöst wird, das es zu verarbeiten gilt, verharrt die Laufzeit im Ruhezustand und wartet auf das nächste. Verantwortlich für den scheinbaren Ruhezustand ist die Nachrichtenschleife, die beim Aufruf der Methode Application.Run eingerichtet wird und ohne Ermüdungserscheinungen zu zeigen permanent auf Ereignisse wartet.
15.4.1 Grundlegende Anmerkungen
 
Rufen wir uns noch einmal ganz allgemein in Erinnerung, wie wir mit C# eine Methode an ein bestimmtes Ereignis eines Objekts binden:
| Objekt.Ereignis += new Delegattyp(Methodenname);
|
Ein Ereignis ist immer vom Typ eines Delegaten. Bei einem Click-Ereignis ist das immer der Delegat EventHandler, andere Ereignisse sind oft von einem anderen Delegate-Typ. Die Definition des Delegaten EventHandler lautet wie folgt:
| public delegate void EventHandler(object sender, EventArgs e);
|
Eine Methode, die das Ereignis behandeln soll, muss dementsprechend eine Parameterliste bestehend aus zwei Parametern definieren. Dabei ist der erste Parameter vom Typ object, der zweite vom Typ EventArgs. Eine akzeptable Methodensignatur könnte in diesem Fall demnach
| private void MeineMethode(object obj, EventArgs e)
|
sein, die beispielsweise mit
| MyComponent.Click += new EventHandler(MeineMethode);
|
an das Click-Ereignis der Komponente MyComponent gebunden wird.
Nun müssen wir die Parameterliste des Delegaten genauer unter die Lupe nehmen. Der erste Parameter beschreibt die Referenz auf ein Objekt. Der Typ ist mit object so allgemein gehalten, dass hier jeder Typ des .NET Frameworks in Frage kommt. Der Name des Parameters lautet sender und deutet damit schon an, dass hier die Referenz auf das ereignisauslösende Objekt übergeben wird. Das ist durchaus sinnvoll, denn der Ereignishandler steht zu dem auslösenden Objekt in einer 1:n-Beziehung, da mit dem »+=«-Operator die Ereignisse verschiedener Objekte mit demselben Ereignishandler verknüpft werden können. Über den ersten Parameter erhält die ereignisbehandelnde Methode also direkten Kontakt zur Ereignisquelle, kann sie möglicherweise abfragen und sogar manipulieren.
Der zweite Parameter im Delegaten EventHandler ist vom Typ EventArgs. Dabei handelt es sich um eine Klasse, die dem Namespace System zugeordnet ist. Grundsätzlich dient der zweite Parameter dazu, dem Ereignisempfänger im Zusammenhang mit dem Ereignis Zustandsinformationen zu übermitteln. EventArgs enthält selbst keine spezifischen Ereignisdaten, weil solche von einem Click-Ereignis nicht übergeben werden. Im Grunde genommen ist EventArgs nichts anderes als ein Dummy. Andere Ereignisse können dem Ereignishandler aber durchaus Dateninformationen zur Verfügung stellen. Dann muss der zweite Parameter konventionsgemäß ein von EventArgs abgeleiteter Typ sein. Die ereignisspezifischen Daten sind die Eigenschaften des zweiten Parameters.
| Hinweis Das .NET Framework folgt bei den Ereignissen der Steuerelemente einer einheitlichen Namenskonvention. Jeder Delegat wird mit EventHandler bezeichnet, der Bezeichner des zweiten Parameters tauscht das Suffix Handler gegen Args aus. Wie Sie gesehen haben, liefert der Parameter EventArgs keine ereignisspezifischen Daten. Das ist aber nicht immer so. Werden dem Ereignishandler über den zweiten Parameter Informationen übergeben, müssen der Delegat und folglich auch das Ereignis von einem anderen Typ sein. Im .NET Framework wird der Typbezeichnung des Delegaten und der des Args-Parameters dazu ein beschreibendes Präfix vorangestellt. Beispielsweise sind die Mausereignisse vom Typ des Delegaten MouseEventHandler, der Parameter ist dann analog vom Typ MouseEventArgs.
|
Das Ereignis Click des Buttons soll nach der oben codierten Verknüpfungsanweisung an eine Methode namens MeineMethode gebunden werden, deren Parameterliste der des Delegaten EventHandler entspricht:
| private void MeineMethode(object sender, EventArgs e) {/*.../*}
|
Den Namen der Methode kann man beliebig festlegen. Nutzen wir den Automatismus der Entwicklungsumgebung, setzt sich der Bezeichner des Ereignishandlers aus dem Namen des Steuerelements und – durch einen Unterstrich getrennt – dem Bezeichner des Ereignisses zusammen, beispielsweise:
| private void btn_Click(object sender, EventArgs e) {/*...*/}
|
Diese automatische Namensvergabe von .NET dient eher einer guten Les- und Wartbarkeit des Programmcodes, sie ist aber keine Festlegung, an die Sie sich halten müssen. Auch wenn durch den Bezeichner des Ereignishandlers suggeriert wird, dass die Methode nur das Click-Ereignis des Objekts btn bedient, kann die Methode mit jedem x-beliebigen Ereignis verknüpft werden – sogar unabhängig vom zweiten Parameter, denn dieser ist vom Typ EventArgs deklariert, der selbst Basisklasse aller anderen Klassen ist, die Ereignisdaten bereitstellen.
15.4.2 Ereignisse mit Ereignisdaten
 
Click hat keine spezifischen Ereignisdaten, andere Ereignisse durchaus. Bewegen Sie zum Beispiel den Mauszeiger über den Clientbereich eines Formulars oder eines Steuerelements, werden in sehr schneller Abfolge MouseMove-Ereignisse ausgelöst, die wie folgt deklariert sind:
| public event MouseEventHandler MouseMove;
|
Der Typ des Ereignisses ist jetzt nicht mehr der Delegat EventHandler, sondern MouseEventHandler. Sehen wir uns dessen Definition an:
| public delegate void MyHandler(object sender, MouseEventArgs e);
|
Eine Methode, die MouseMove-Ereignisse behandeln soll, empfängt im ersten Parameter ebenfalls die Referenz auf das ereignisauslösende Objekt, im zweiten die Referenz auf ein Objekt vom Typ der Klasse MouseEventArgs, das ereignisspezifische Zustandsdaten enthält.
Mit einem Ereignishandler auf dieses Ereignis zu reagieren ist dann sinnvoll, wenn die aktuelle Position des Mauszeigers ausgewertet werden muss. Eine MouseEventArgs-Instanz veröffentlicht zwei Eigenschaften, welche die aktuelle Position wiedergeben: X und Y.
Tabelle 15.2 Eigenschaften der Klasse »MouseEventArgs«
| Eigenschaft
|
Beschreibung
|
| X
|
Liefert die x-Koordinate des Mauszeigers.
|
| Y
|
Liefert die y-Koordinate des Mauszeigers.
|
Das folgende Codefragment zeigt, wie Sie diese Daten auswerten können. Es handelt sich bei der Methode um den Ereignishandler des MouseMove-Ereignisses der Form. Zur Laufzeit bewirkt dieser Code, dass während des Ziehens des Mauszeigers über der Form in der Titelleiste permanent die aktuelle x- und y-Position angezeigt wird.
| private void Form1_MouseMove(object sender, MouseEventArgs e) {
|
| int xPosition = e.X;
|
| int yPosition = e.Y;
|
| this.Text = "X=" + yPosition + " / Y=" + yPosition;
|
| }
|
15.4.3 Ereignishandler mit dem Visual Studio 2005 bereitstellen
 
Grundsätzlich können Sie Ereignisse an jeder Stelle im Programmcode an Ereignishandler binden oder, wenn erforderlich, diese auch wieder lösen. In den meisten Fällen werden Sie aber die Fähigkeiten der Entwicklungsumgebung nutzen wollen, die Ihnen alle denkbaren Möglichkeiten bietet.
Ein Doppelklick im Forms-Designer bewirkt, dass das Visual Studio eine Methode für das Standardereignis der angeklickten Komponente erstellt. Um abweichend vom Standardereignis ein anderes zu behandeln, können Sie das Eigenschaftsfenster einsetzen, nachdem Sie in der Symbolleiste auf die Ereignisliste umgeschaltet haben (siehe Abbildung 15.4).
Klicken Sie auf ein Ereignis in der Ereignisliste, das Sie behandeln wollen, doppelt, wird automatisch ein Ereignishandler erstellt, dessen Bezeichner sich aus Objektnamen und Ereignis zusammensetzt. Die Bindung des Ereignishandlers finden Sie in InitializeComponent. Sie können dem Ereignishandler auch eine Bezeichnung geben, die von der üblichen Vorgabe abweicht. Dazu tragen Sie nur den von Ihnen bevorzugten Methodennamen in der Wertespalte neben dem Ereignis manuell ein.
Möchten Sie einen Ereignishandler gleichzeitig für mehrere Ereignisse registrieren, ist das auch sehr einfach zu lösen. Aktivieren Sie die Wertespalte zu einem Ereignis in der Ereignisliste, sehen Sie eine Schaltfläche mit einem Pfeilsymbol. Sie können darüber eine Liste öffnen, in der alle Ereignishandler aufgeführt sind, die im Args-Parameter denselben Typ haben (siehe Abbildung 15.7). Wählen Sie daraus den Ereignishandler aus, der das markierte Ereignis verarbeiten soll.
 Hier klicken, um das Bild zu vergrößern
Abbildung 15.7 Auswahl aus der Liste der Ereignishandler
Beispielprogramm
Unsere oben entwickelte Windows-Anwendung soll nun um die Verarbeitung von Mausereignissen, ähnlich wie oben schon gezeigt, erweitert werden. Programmiert werden die MouseMove-Ereignisse der Form und der beiden Buttons. Dabei soll in der Titelleiste der Form angezeigt werden, welche Komponente die Koordinaten x und y liefert, die ebenfalls mit angezeigt werden. Da das Verhalten der Anwendung bei allen drei Mausereignissen identisch ist, drängt sich ein gemeinsamer Ereignishandler förmlich auf.
| // --------------------------------------------------------------
|
| // Beispiel: ...\Kapitel 15\MausereignisDemo
|
| // --------------------------------------------------------------
|
| private void Form1_MouseMove(object sender, MouseEventArgs e) {
|
| string koordinate = "x = " + e.X + ", y = " + e.Y;
|
| if (sender == this)
|
| this.Text = "Form / " + koordinate;
|
| else if (sender == btnKopieren)
|
| this.Text = "Kopieren / " + koordinate;
|
| else
|
| this.Text = "Beenden / " + koordinate;
|
| }
|
Über sender können wir auswerten, welche Komponente das Ereignis ausgelöst hat. Dazu wird der erste Parameter mit this beziehungsweise den Referenzen btnKopieren und btnBeenden verglichen.
Als Ursprung der Koordinatenmessung ist standardmäßig der linke obere Eckpunkt des Clientbereichs der jeweiligen Komponente festgelegt. Deshalb macht bei der Ausführung des Programms die Koordinatenanzeige auch einen Zahlensprung, sobald der Mauszeiger aus dem Clientbereich der Form in den Clientbereich der Schaltfläche oder umgekehrt wechselt.
15.4.4 Ereignisbehandlung mit den »OnXxx«-Methoden
 
Ein Ereignis kann durch die Installation einer Ereignisbehandlungsroutine behandelt werden. Alternativ dazu gibt es aber auch noch eine weitere Möglichkeit. Sehen Sie sich dazu den Code der Klasse MyForm an:
| public partial class MyForm : Form {
|
| protected override void OnClick(EventArgs e) {
|
| this.Text = "In MyForm";
|
| }
|
| }
|
Jede grafische Windows-Komponente beerbt die Klasse Control. Dazu gehören auch Methoden, die einem Ereignisnamen entsprechen, ergänzt um das Präfix On. In der Basisklasse Control ist die Methode OnClick folgendermaßen definiert:
| protected virtual void OnClick(EventArgs e);
|
Interessant ist die Parameterliste, die nur ein Argument aufweist, nämlich das, um spezifische Zustandsinformationen des Ereignisses bereitzustellen. Was fehlt, ist das Argument, das die Referenz auf den Auslöser liefert. Daraus lässt sich schlussfolgern, dass alle OnXxx-Methoden immer auf die aktuelle Komponente aufgerufen werden. Es sind Ereignisempfänger, die spezifisch für ein ganz bestimmtes Objekt sind und nicht von einer anderen Komponente gleichzeitig zur Behandlung eines ausgelösten Ereignisses benutzt werden können. In der Basisklasse Control sind alle OnXxx-Methoden virtual deklariert und müssen deshalb mit override überschrieben werden.
Doch wozu dienen die OnXxx-Methoden? Diese Frage kann mit einem Satz beantwortet werden:
| Methoden, deren Bezeichner vor dem Ereignisnamen das Präfix On aufweisen, sind für den Aufruf aller installierten Ereignishandler zuständig.
|
Das ist eine Behauptung, die natürlich auch bewiesen werden muss. Deshalb entwickeln wir eine Klasse, die aus Form abgeleitet ist, die OnClick-Methode der Basisklasse überschreibt und beim Klicken auf den Clientbereich in die Titelleiste des Formulars den Text »... in der OnClick-Methode« schreibt. Außerdem wird ein Ereignishandler installiert, der zusätzlich mit einer ergänzenden Titelleistenanzeige auf das Ereignis Click reagieren soll.
| // --------------------------------------------------------------
|
| // Beispiel: ...\Kapitel 15\OnClickDemo
|
| // --------------------------------------------------------------
|
| public partial class Form1 : Form {
|
| public Form1() {
|
| InitializeComponent();
|
| this.Text = "";
|
| }
|
| private void Form1_Click(object sender, EventArgs e) {
|
| ((Form1)sender).Text = "Im Ereignishandler ";
|
| }
|
| protected override void OnClick(EventArgs e) {
|
| // base.OnClick(e);
|
| this.Text += " ... in der OnClick-Methode";
|
| }
|
| }
|
Beachten Sie bitte, dass eine Anweisung in OnClick auskommentiert ist.
Starten Sie diese Anwendung, wird in die Titelleiste zwar der Text »... in der OnClick-Methode« geschrieben, aber der Text, der aus dem Click-Ereignishandler ebenfalls in Titelleiste geschrieben werden soll, wird definitiv nicht angezeigt. Der Ereignishandler wird also überhaupt nicht aufgerufen.
Heben Sie die Kommentierung in OnClick auf, wird die Anzeige in der Titelleiste »Im Ereignishandler ... in der OnClick-Methode« lauten. Mit base.OnClick() wird die ursprüngliche Methode in der Basisklasse aufgerufen. Damit ist die Aufgabe, die der virtuell deklarierten OnClick-Methode zukommt, nachgewiesen: Sie hat dafür gesorgt, dass unser installierter Ereignishandler ausgeführt wird.
| Der Aufruf der überschriebenen Methode in der Basisklasse garantiert die ursprüngliche Funktionalität. Um keine Einbußen durch das Überschreiben einer OnXxx-Methode in Kauf nehmen zu müssen, sollte daher grundsätzlich in der überschreibenden Methode mit base die überschriebene ausgeführt werden.
|
|